Skip to content

Transport level retry - #954

Merged
l-trotta merged 15 commits into
mainfrom
transport-level-retry
Jul 30, 2026
Merged

Transport level retry#954
l-trotta merged 15 commits into
mainfrom
transport-level-retry

Conversation

@l-trotta

@l-trotta l-trotta commented Mar 3, 2025

Copy link
Copy Markdown
Contributor

Adds new retry functionality to the client, configurable like so:

try (ElasticsearchClient client = ElasticsearchClient.of(e -> e
    .host(serverUrl)
    .apiKey(apikey)
    .retryConfig(r -> r
        .backoffPolicy(BackoffPolicy.constantBackoff(5000L,5)) // defaults to BackoffPolicy.noBackoff()
        .retryableExceptions(Set.of(SocketException.class)) // defaults to IOException.class
        .retryableStatuses(429,503) // defaults to 429, 500, 502, 503, 504
    )
)) 

Or if only a single request needs to be retried:

try (ElasticsearchClient client = ElasticsearchClient.of(e -> e
    .host(serverUrl)
    .apiKey(apikey)
)) {
    client.withTransportOptions(o -> o
        .retryConfig(r -> r
            .backoffPolicy(BackoffPolicy.constantBackoff(5000L,5))
        ))
    .ping();
}

The delegate client (Rest5Client, RestClient or any custom client) is wrapped in an instance of RetryingHttpClient, which takes care of the retrying logic according to the configuration.
This only concerns the same node the request was originally sent to, it's still the underlying client's responsibility to handle dead node logic and node selection.

@l-trotta
l-trotta requested a review from swallez March 3, 2025 17:00

@swallez swallez left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some comments on implementation independence and async tasks.

Adding this to TransportOptions is an interesting choice, as it allows using different retry policies with a single transport.

@l-trotta
l-trotta force-pushed the transport-level-retry branch from be599ac to ba8a26b Compare July 30, 2026 15:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces transport-level retry support in the Java client by adding a configurable RetryConfig (backoff + retryable statuses/exceptions) and routing requests through a new RetryingHttpClient wrapper when retries are enabled (globally or per-request). It also updates the low-level client adapters so status-carrying exceptions (e.g., ResponseException) can be classified by HTTP status for retry decisions.

Changes:

  • Add RetryConfig and TransportOptions#retryConfig() to configure retry behavior at the client or request level.
  • Introduce RetryingHttpClient and integrate it into ElasticsearchTransportBase with lazy, shared wrapping.
  • Add/extend tests to validate retry behavior (status-based, exception-based, cancellation) across mocked and real HTTP clients.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
java-client/src/test/java/co/elastic/clients/transport/TransportRetryStatusTest.java New integration tests validating status-based retries via real low-level clients.
java-client/src/test/java/co/elastic/clients/transport/http/RetryingHttpClientTest.java New unit tests for retry classification and retry/cancellation behavior.
java-client/src/test/java/co/elastic/clients/transport/ElasticsearchTransportRetryTest.java New tests validating transport-level routing (client-level vs per-request retry config).
java-client/src/test/java/co/elastic/clients/transport/ElasticsearchTransportConfigTest.java Adds coverage for the new builder shortcut wiring retry config into transport options.
java-client/src/main/java/co/elastic/clients/transport/TransportOptions.java Adds retryConfig() default + builder hooks to carry retry config through request options.
java-client/src/main/java/co/elastic/clients/transport/RetryConfig.java New retry configuration type (backoff + retryable statuses/exceptions).
java-client/src/main/java/co/elastic/clients/transport/rest5_client/Rest5ClientOptions.java Persists/copies retry config into rest5 transport options.
java-client/src/main/java/co/elastic/clients/transport/rest5_client/Rest5ClientHttpClient.java Exposes status codes from rest5 ResponseException for retry classification.
java-client/src/main/java/co/elastic/clients/transport/rest_client/RestClientOptions.java Persists/copies retry config into legacy rest transport options.
java-client/src/main/java/co/elastic/clients/transport/rest_client/RestClientHttpClient.java Exposes status codes from legacy ResponseException for retry classification.
java-client/src/main/java/co/elastic/clients/transport/http/TransportHttpClient.java Adds responseStatusCode(Throwable) hook for wrapper-agnostic status extraction.
java-client/src/main/java/co/elastic/clients/transport/http/RetryingHttpClient.java New retrying wrapper implementation (sync/async + backoff scheduling + cancellation).
java-client/src/main/java/co/elastic/clients/transport/ElasticsearchTransportConfig.java Adds builder shortcuts to configure retries via the top-level client builder.
java-client/src/main/java/co/elastic/clients/transport/ElasticsearchTransportBase.java Routes requests through a shared retry wrapper when retries are enabled.
java-client/src/main/java/co/elastic/clients/transport/DefaultTransportOptions.java Adds retry config storage and builder support in the default options implementation.
java-client/src/main/java/co/elastic/clients/transport/BackoffPolicy.java Fixes minor doc typos.
Comments suppressed due to low confidence (2)

java-client/src/main/java/co/elastic/clients/transport/http/RetryingHttpClient.java:242

  • isRetryableFailure only checks err and one getCause() level for a status-carrying exception. If the low-level client's ResponseException is wrapped more deeply (common with async frameworks), the retry decision can fall back to exception-type matching and incorrectly retry / not retry. Consider walking the full cause chain when extracting a status code.
        Integer status = delegate.responseStatusCode(err);
        if (status == null && err.getCause() != null) {
            status = delegate.responseStatusCode(err.getCause());
        }
        if (status != null) {

java-client/src/main/java/co/elastic/clients/transport/http/RetryingHttpClient.java:250

  • RetryConfig docs say wrapped exceptions are considered, but isRetryableException only checks the exception itself and a single cause. This can miss retryable causes nested deeper in the chain (e.g., CompletionException -> RuntimeException -> IOException). Iterate through the full cause chain until null/self-reference.
    boolean isRetryableException(RetryConfig config, Throwable err) {
        return matchesRetryable(config, err) || matchesRetryable(config, err.getCause());
    }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@l-trotta
l-trotta merged commit 2cb423b into main Jul 30, 2026
23 checks passed
@l-trotta
l-trotta deleted the transport-level-retry branch July 30, 2026 16:09
l-trotta added a commit that referenced this pull request Jul 30, 2026
* rewriting global retry, addressing review

* refactoring retrying logic

* add logs

* minimal test refactor

* add configurable exception logic

* comments

* code style

* simplify test

* single request retry

* bugfixes, fetching inner status code

* refactor, simplify

* better comments

* more simplifying comments

* fix rebase

* address copilot review

Co-authored-by: Laura Trotta <153528055+l-trotta@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants